package 注解;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Inherited;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;

//Java 注释
//
//以前，『XML』是各大框架的青睐者，它以松耦合的方式完成了框架中几乎所有的配置，但是随着项目越来越庞大，『XML』的内容也越来越复杂，维护成本变高。
//
//于是就有人提出来一种标记式高耦合的配置方式，『注解』。方法上可以进行注解，类上也可以注解，字段属性上也可以注解，反正几乎需要配置的地方都可以进行注解。
//
//关于『注解』和『XML』两种不同的配置模式，争论了好多年了，各有各的优劣，注解可以提供更大的便捷性，易于维护修改，但耦合度高，而 XML 相对于注解则是相反的。
//
//追求低耦合就要抛弃高效率，追求效率必然会遇到耦合。本文意不再辨析两者谁优谁劣，而在于以最简单的语言介绍注解相关的基本内容。
//
//《注解的本质》
//「java.lang.annotation.Annotation」接口中有这么一句话，用来描述『注解』。
//
//The common interface extended by all annotation types 所有的注解类型都继承自这个普通的接口（Annotation）
//接口 java.lang.annotation.Annotation 是所有注解继承的接口,并且是自动继承，不需要定义时指定，类似于所有类都自动继承Object。
//public interface Annotation {
//
//    boolean equals(Object obj);
//
//    int hashCode();
//
//    String toString();
//
//    Class<? extends Annotation> annotationType();
//}
//
//这句话有点抽象，但却说出了注解的本质。我们看一个 JDK 内置注解的定义：
//
//@Target(ElementType.METHOD)
//@Retention(RetentionPolicy.SOURCE)
//public @interface Override {
//}
//这是注解 @Override 的定义，其实它本质上就是：
//
//public interface Override extends Annotation{
//}
//没错，注解的本质就是一个继承了 Annotation 接口的接口。有关这一点，你可以去反编译任意一个注解类，你会得到结果的。
//
//一个注解准确意义上来说，只不过是一种特殊的注释而已，如果没有解析它的代码，它可能连注释都不如。
//
//而解析一个类或者方法的注解往往有两种形式，一种是编译期直接的扫描，一种是运行期反射。反射的事情我们待会说，而编译器的扫描指的是编译器在对java代码编译字
//节码的过程中会检测到某个类或者方法被一些注解修饰，这时它就会对于这些注解进行某些处理。
//
//典型的就是注解 @Override，一旦编译器检测到某个方法被修饰了 @Override 注解，编译器就会检查当前方法的方法签名是否真正重写了父类的某个方法，也就是比较父
//类中是否具有一个同样的方法签名。
//
//这一种情况只适用于那些编译器已经熟知的注解类，比如 JDK 内置的几个注解，而你自定义的注解，编译器是不知道你这个注解的作用的，当然也不知道该如何处理，往往只是
//会根据该注解的作用范围来选择是否编译进字节码文件，仅此而已。
//
//《元注解》
//『元注解』是用于修饰注解的注解，通常用在注解的定义上，例如：
//
//@Target(ElementType.METHOD)
//@Retention(RetentionPolicy.SOURCE)
//public @interface Override {}
//这是我们 @Override 注解的定义，你可以看到其中的 @Target，@Retention 两个注解就是我们所谓的『元注解』，『元注解』一般用于指定某个注解生命周期以及作用目标等信息。
//
//JAVA 中有以下几个『元注解』：
//
//@Target：注解的作用目标
//@Retention：注解的生命周期
//@Documented：注解是否应当被包含在 JavaDoc 文档中
//@Inherited：是否允许子类继承该注解
//其中，@Target 用于指明被修饰的注解最终可以作用的目标是谁，也就是指明，你的注解到底是用来修饰方法的？修饰类的？还是用来修饰字段属性的。
//
//《@Target 的定义如下：》
//
//@Documented
//@Retention(RetentionPolicy.RUNTIME)
//@Target(ElementType.ANNOTATION_TYPE)
//public @interface Target{
//	ElementType[] value();
//}
//
//我们可以通过以下的方式来为这个 value 传值：
//
//@Target(value = {ElementType.FIELD})
//被这个 @Target 注解修饰的注解将只能作用在成员字段上，不能用于修饰方法或者类。其中，ElementType 是一个枚举类型，有以下一些值：
//
//ElementType.TYPE：允许被修饰的注解作用在类、接口和枚举上
//ElementType.FIELD：允许作用在属性字段上
//ElementType.METHOD：允许作用在方法上
//ElementType.PARAMETER：允许作用在方法参数上
//ElementType.CONSTRUCTOR：允许作用在构造器上
//ElementType.LOCAL_VARIABLE：允许作用在本地局部变量上
//ElementType.ANNOTATION_TYPE：允许作用在注解上
//ElementType.PACKAGE：允许作用在包上
//
//
//《@Retention 用于指明当前注解的生命周期，它的基本定义如下：》
//
//@Documented
//@Retention(RetentionPolicy.RUNTIME)
//@Target(ElementType.ANNOTATION_TYPE)
//public @interface Retention{
//	RetentionPolicy value();
//}
//同样的，它也有一个 value 属性：
//
//@Retention(value = RetentionPolicy.RUNTIME)
//这里的 RetentionPolicy 依然是一个枚举类型，它有以下几个枚举值可取：
//
//RetentionPolicy.SOURCE：当前注解编译期可见，不会写入 class 文件
//RetentionPolicy.CLASS：类加载阶段丢弃，会写入 class 文件
//RetentionPolicy.RUNTIME：永久保存，可以反射获取
//
//@Retention 注解指定了被修饰的注解的生命周期，一种是只能在编译期可见，编译后会被丢弃，一种会被编译器编译进 class 文件中，无论是类或是方法，
//乃至字段，他们都是有属性表的，而 JAVA 虚拟机也定义了几种注解属性表用于存储注解信息，但是这种可见性不能带到方法区，类加载时会予以丢弃，最后一
//种则是永久存在的可见性。
//
//剩下两种类型的注解我们日常用的不多，也比较简单，这里不再详细的进行介绍了，你只需要知道他们各自的作用即可。@Documented 注解修饰的注解，当我们
//执行 JavaDoc 文档打包时会被保存进 doc 文档，反之将在打包时丢弃。@Inherited 注解修饰的注解是具有可继承性的，也就说我们的注解修饰了一个类，
//而该类的子类将自动继承父类的该注解。
//
//JAVA 的内置三大注解
//除了上述四种元注解外，JDK 还为我们预定义了另外三种注解，它们是：
//
//@Override
//@Deprecated
//@SuppressWarnings
//
//《@Override 注解想必是大家很熟悉的了，它的定义如下：》
//
//@Target(ElementType.METHOD)
//@Retention(RetentionPolicy.SOURCE)
//public @interface Override {
//}
//它没有任何的属性，所以并不能存储任何其他信息。它只能作用于方法之上，编译结束后将被丢弃。
//
//所以你看，它就是一种典型的『标记式注解』，仅被编译器可知，编译器在对 java 文件进行编译成字节码的过程中，一旦检测到某个方法上被修饰了该注解，就会去匹对
//父类中是否具有一个同样方法签名的函数，如果不是，自然不能通过编译。
//
//《@Deprecated 的基本定义如下：》
//
//@Documented
//@Retention(RetentionPolicy.RUNTIME)
//@Target(value= {CONSTRUCTOR,FIELD,LOCAL_VARIABLE,METHOD,PACKAGE,PARAMETER,TYPE})
//public @interface Deprecated {
//}
//
//依然是一种『标记式注解』，永久存在，可以修饰所有的类型，作用是，标记当前的类或者方法或者字段等已经不再被推荐使用了，可能下一次的 JDK 版本就会删除。
//
//当然，编译器并不会强制要求你做什么，只是告诉你 JDK 已经不再推荐使用当前的方法或者类了，建议你使用某个替代者。
//
//《@SuppressWarnings 主要用来压制 java 的警告，它的基本定义如下：》
//
//@Target(value= {TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE})
//@Retention(RetentionPolicy.SOURCE)
//public @interface SuppressWarnings {
//	String[] value();
//}
//
//它有一个 value 属性需要你主动的传值，这个 value 代表一个什么意思呢，这个 value 代表的就是需要被压制的警告类型。例如：
//
//public static void main(String[] args) {
//    Date date = new Date(2018, 7, 11);
//}
//这么一段代码，程序启动时编译器会报一个警告。
//
//Warning:(8, 21) java: java.util.Date 中的 Date(int,int,int) 已过时
//
//而如果我们不希望程序启动时，编译器检查代码中过时的方法，就可以使用 @SuppressWarnings 注解并给它的 value 属性传入一个参数值来压制编译器的检查。
//
//@SuppressWarning(value = "deprecated")
//public static void main(String[] args) {
//    Date date = new Date(2018, 7, 11);
//}
//这样你就会发现，编译器不再检查 main 方法下是否有过时的方法调用，也就压制了编译器对于这种警告的检查。
//
//当然，JAVA 中还有很多的警告类型，他们都会对应一个字符串，通过设置 value 属性的值即可压制对于这一类警告类型的检查。
//
//自定义注解的相关内容就不再赘述了，比较简单，通过类似以下的语法即可自定义一个注解。
//
//public @interface InnotationName{
//}
//当然，自定义注解的时候也可以选择性的使用元注解进行修饰，这样你可以更加具体的指定你的注解的生命周期、作用范围等信息。
//
//《注解与反射》
//上述内容我们介绍了注解使用上的细节，也简单提到，「注解的本质就是一个继承了 Annotation 接口的接口」，现在我们就来从虚拟机的层面看看，注解的本质到底是什么。
//
//首先，我们自定义一个注解类型：
//
//@Target(value= {ElementType.FIELD，ElementType.METHOD})
//@Retention(value=RetentionPolicy.RUNTIME)
//public @interface Hello {
//	String value();
//}
//
//这里我们指定了 Hello 这个注解只能修饰字段和方法，并且该注解永久存活，以便我们反射获取。
//
//之前我们说过，虚拟机规范定义了一系列和注解相关的属性表，也就是说，无论是字段、方法或是类本身，如果被注解修饰了，就可以被写进字节码文件。属性表有以下几种：
//
//RuntimeVisibleAnnotations：运行时可见的注解
//RuntimeInVisibleAnnotations：运行时不可见的注解
//RuntimeVisibleParameterAnnotations：运行时可见的方法参数注解
//RuntimeInVisibleParameterAnnotations：运行时不可见的方法参数注解
//AnnotationDefault：注解类元素的默认值
//
//给大家看虚拟机的这几个注解相关的属性表的目的在于，让大家从整体上构建一个基本的印象，注解在字节码文件中是如何存储的。
//
//所以，对于一个类或者接口来说，Class 类中提供了以下一些方法用于反射注解。
//
//getAnnotation：返回指定的注解
//isAnnotationPresent：判定当前元素是否被指定注解修饰
//getAnnotations：返回所有的注解
//getDeclaredAnnotation：返回本元素的指定注解
//getDeclaredAnnotations：返回本元素的所有注解，不包含父类继承而来的
//
//方法、字段中相关反射注解的方法基本是类似的，这里不再赘述，我们下面看一个完整的例子。
//
//首先，设置一个虚拟机启动参数，用于捕获 JDK 动态代理类。
//
//-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
//
//然后 main 函数。
//
//public class Test{
//	@Hello("hello")
//	public static void main(String[] args) throws NoSuchMethodException {
//		Class cls=Test.class;
//		Method method=cls.getMethod(name:"main",String[].class);
//		Hello hello=method.getAnnotation(hello.class);
//	}
//}
//
//我们说过，注解本质上是继承了 Annotation 接口的接口，而当你通过反射，也就是我们这里的 getAnnotation 方法去获取一个注解类实例的时候，
//其实 JDK 是通过动态代理机制生成一个实现我们注解（接口）的代理类。
//
//那么这样，一个注解的实例就创建出来了，它本质上就是一个代理类，你应当去理解好 AnnotationInvocationHandler 中 invoke 方法的实现逻辑，这是核心。
//一句话概括就是，通过方法名返回注解属性值。

//二、使用注解
//定义一个注解，并需要实现一个方法：
/*
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Test {
    int value();
}
//在类中使用，需要传入value的值，可以通过反射获取：

@Test(100)
public class 注解 {
    public static void main(String[] args) throws ClassNotFoundException {
    	Class<?> cls=Class.forName("注解.注解");
        //Class<?> cls = 注解.class;
        Test annotation = cls.getAnnotation(Test.class);
        int value = annotation.value();
        System.out.println(value);
    }
}
*/
//注解无法改变代码本身，注解只是某些工具的的工具。注解主要针对的是编译器和其它工具软件(SoftWare tool)。
//
//当开发者使用了Annotation 修饰了类、方法、Field 等成员之后，这些 Annotation 不会自己生效，必须由开发者提供相应的代码来提取并处理 Annotation 信息。
//这些处理提取和处理 Annotation 的代码统称为 APT（Annotation Processing Tool)。
//
//注解是给编译器或者 APT 用的。
//
//我要写一个测试框架，测试程序员的代码有无明显的异常。
//
//—— 程序员 A : 我写了一个类，它的名字叫做 NoBug，因为它所有的方法都没有错误。
//—— 我：自信是好事，不过为了防止意外，让我测试一下如何？
//—— 程序员 A: 怎么测试？
//—— 我：把你写的代码的方法都加上 @Jiecha 这个注解就好了。
//—— 程序员 A: 好的。

//package ceshi;
//import ceshi.Jiecha;
/*class NoBug {
    @Jiecha
    public void suanShu(){
        System.out.println("1234567890");
    }
    @Jiecha
    public void jiafa(){
        System.out.println("1+1="+1+1);
    }
    @Jiecha
    public void jiefa(){
        System.out.println("1-1="+(1-1));
    }
    @Jiecha
    public void chengfa(){
        System.out.println("3 x 5="+ 3*5);
    }
    @Jiecha
    public void chufa(){
        System.out.println("6 / 0="+ 6 / 0);
    }
    public void ziwojieshao(){
        System.out.println("我写的程序没有 bug!");
    }
}
//上面的代码，有些方法上面运用了 @Jiecha 注解。
//
//这个注解是我写的测试软件框架中定义的注解。

//package ceshi;
//import java.lang.annotation.Retention;
//import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@interface Jiecha {
}
//然后，我再编写一个测试类 TestTool 就可以测试 NoBug 相应的方法了。

//package ceshi;
//import java.lang.reflect.InvocationTargetException;
//import java.lang.reflect.Method;
//public class TestTool {
public class 注解 {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        NoBug testobj = new NoBug();
        Class<?> clazz = testobj.getClass();
        Method[] method = clazz.getDeclaredMethods();
        //用来记录测试产生的 log 信息
        StringBuilder log = new StringBuilder();
        // 记录异常的次数
        int errornum = 0;
        for ( Method m: method ) {
            // 只有被 @Jiecha 标注过的方法才进行测试
            if (m.isAnnotationPresent(Jiecha.class)) {
                try {
                    m.setAccessible(true);
                    m.invoke(testobj, null);
                } catch (Exception e) {
                    //e.printStackTrace();
                    errornum++;
                    log.append(m.getName());
                    log.append(" ");
                    log.append("has error:");
                    log.append("\n  caused by ");
                    //记录测试过程中，发生的异常的名称
                    log.append(e.getCause().getClass().getSimpleName());
                    log.append("\n");
                    //记录测试过程中，发生的异常的具体信息
                    log.append(e.getCause().getMessage());
                    log.append("\n\r");
                } 
            }
        }
        log.append(clazz.getSimpleName());
        log.append(" has  ");
        log.append(errornum);
        log.append(" error.");
        // 生成测试报告
        System.out.println(log.toString());
    }
}*/
//测试的结果是：

//提示 NoBug 类中的 chufa() 这个方法有异常，这个异常名称叫做 ArithmeticException，原因是运算过程中进行了除 0 的操作。
//
//所以，NoBug 这个类有 Bug。
//
//这样，通过注解我完成了我自己的目的，那就是对别人的代码进行测试。
//
//所以，再问我注解什么时候用？我只能告诉你，这取决于你想利用它干什么用。
//
//自定义注解实现一个sql语句的拼接
//需要注意的方面：注解的定义类似于接口的定义，使用@interface来定义，定义一个方法即为注解类型定义了一个元素，方法的声明不允许有参数或throw语句，
//返回值类型被限定为原始数据类型、字符串String、Class、enums、注解类型，或前面这些的数组，方法可以有默认值。
//
//自定义注解一般可以分为三步： 定义注解，使用注解，读取注解
//
//《定义注解》

@Target(ElementType.TYPE) //注解加载类上
@Retention(RetentionPolicy.RUNTIME) // 运行时读取注解
@interface Table {//含一个元素：
    String value(); 
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface UserFiled {//含三个元素：
    String name();
    String type();
    int length();
}

//《使用注解》

// 将自定义的注解加在用户上,实现一个表的映射
@Table(value = "user_table")
class User {

    @UserFiled(name = "user_id",type = "int",length = 8)
    private int userId;

    @UserFiled(name = "user_name",type = "varchar",length = 16)
    private String userName;

    @UserFiled(name = "password",type = "varchar",length = 16)
    private String password;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

//《读取注解的内容》

//读取注解中的值
public class 注解 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        //Class userClass = Class.forName("注解.User");
        Class<?> userClass = User.class;
        Table table=null;
        // 读取类上的注解
        System.out.println(userClass.isAnnotationPresent(Table.class));//true  判断userClass拥有注解Table
        try {
        	table = (Table) userClass.getAnnotation(Table.class);//获取userClass的注解
        	System.out.println(table.value());//注解元素值
        }catch (Exception e) {   
    		e.printStackTrace();
        } 

        // 读取属性上注解
        Field userId = userClass.getDeclaredField("userId");
        System.out.println(userId.isAnnotationPresent(UserFiled.class));//true  判断userId拥有注解:UserFiled
        UserFiled userFiledId = userId.getAnnotation(UserFiled.class);//获取userId的注解
        System.out.println(userFiledId.length() + "----" + userFiledId.type() + "-----" + userFiledId.name());

        Field userName = userClass.getDeclaredField("userName");
        UserFiled userFiledName = userName.getAnnotation(UserFiled.class);
        System.out.println(userFiledName.length()+"----"+userFiledName.type()+"----"+userFiledName.name());

        Field password = userClass.getDeclaredField("password");
        UserFiled userFiledPassword = password.getAnnotation(UserFiled.class);
        System.out.println(userFiledPassword.name() + "-----" + userFiledPassword.type() + "-----" + userFiledPassword.length());

        // 拼接一个sql语句
        String name = "chenwei";
        String sql ="select * from " + table.value()+" where "+userFiledName.name()+"="+name;
        System.out.println(sql);
    }
}

//结果：
//
//user_table
//user_id----int-----8
//user_name----varchar----16
//password-----varchar-----16
//自定义注解的实现过程：
//
//1，定义注解
//
//2，使用注解，根据自己定义的注解来达到一些目的，本例中，就是使用注解来完成数据库表和实体类的映射关系
//
//3 读取注解的内容，也是比较重要的一部分，核心还是利用了反射的思想，得到使用注解的这个类，根据类中的getAnnotion的方法得到定义的注解，获得注解上的值。
//
//3 注解的实现的原理
//注解的实现的原理很大的一部分是基于反射实现。
//
//​ 反射可以获取到Class对象，进而获取到Constructor、Field、Method等实例，点开源码结构发现Class、Constructor、Field、Method等均实现
//了AnnotatedElement接口，AnnotatedElement接口的方法如下
//
//// 判断该元素是否包含指定注解，包含则返回true
//boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
//    
//// 返回该元素上对应的注解，如果没有返回null
//<T extends Annotation> T getAnnotation(Class<T> annotationClass);
//
//// 返回该元素上的所有注解，如果没有任何注解则返回一个空数组
//Annotation[] getAnnotations();
//
//// 返回指定类型的注解，如果没有返回空数组
//T[] getAnnotationsByType(Class<T> annotationClass)
//    
//// 返回指定类型的注解，如果没有返回空数组，只包含直接标注的注解，不包含inherited的注解
//T getDeclaredAnnotation(Class<T> annotationClass)
//    
//// 返回指定类型的注解，如果没有返回空数组，只包含直接标注的注解，不包含inherited的注解
//T[] getDeclaredAnnotationsByType
//    
//// 返回该元素上的所有注解，如果没有任何注解则返回一个空数组，只包含直接标注的注解，不包含inherited的注解
//Annotation[] getDeclaredAnnotations();
//通过一个实例再次说明一下注解的使用过程：
//
//定义注解
//
//@Documented
//@Target({ElementType.TYPE, ElementType.FIELD,ElementType.METHOD})
//@Retention(RetentionPolicy.RUNTIME)
//public @interface MyAnnotaion {
//    String getValue() default "this is myAnntaion";
//    int order() default 0;
//}
//使用注解
//
//@MyAnnotaion(getValue = "annotation on class")
//public class Demo {
//
//    @MyAnnotaion(getValue = "annotation on filed")
//    public String name;
//
//    @MyAnnotaion(getValue = "annotation on method")
//    public void hello(){
//    }
//
//    @MyAnnotaion
//    public void defaultMethod(){
//
//    }
//}
//
//利用反射读取注解中的值。
//
//public class TestDemo {
//    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
//        /**
//         * 获取类上的注解
//         */
//        Class<Demo> demoClass = Demo.class;
//        Annotation[] annotaion = demoClass.getAnnotations();
//        printAnnotation(annotaion);
//
//        /**
//         * 读取成员变量上的注解
//         */
//        Field name = demoClass.getField("name");
//        Annotation[] getOnFiled = name.getAnnotations();
//        printAnnotation(getOnFiled);
//
//        /**
//         * 读取方法上的注解
//         */
//        Method hello = demoClass.getMethod("hello", null);
//        MyAnnotaion onMethod = hello.getAnnotation(MyAnnotaion.class);
//        System.out.println(onMethod.getValue());
//
//        /**
//         * 获取默认方法上的注解
//         */
//        Method defaultMethod = demoClass.getMethod("defaultMethod", null);
//        MyAnnotaion onDefaultMethod = defaultMethod.getAnnotation(MyAnnotaion.class);
//        System.out.println(onDefaultMethod.getValue());
//
//    }
//
//    public static void printAnnotation(Annotation... annotations) {
//        for (Annotation annotation : annotations) {
//            System.out.println(annotation);
//        }
//    }
//}
//
//运行结果
//
//@annocation.MyAnnotaion(getValue=annotation on class, order=0)
//@annocation.MyAnnotaion(getValue=annotation on filed, order=0)
//annotation on method
//this is myAnntaion
//


//
//注解应用实例
//注解运用的地方太多了，如：
//JUnit 这个是一个测试框架，典型使用方法如下：
//
//public class ExampleUnitTest {
//    @Test
//    public void addition_isCorrect() throws Exception {
//        assertEquals(4, 2 + 2);
//    }
//}
//@Test 标记了要进行测试的方法 addition_isCorrect().
//
//还有例如ssm框架等运用了大量的注解。
//
//总结
//
//如果注解难于理解，你就把它类同于标签，标签为了解释事物，注解为了解释代码。
//注解的基本语法，创建如同接口，但是多了个 @ 符号。
//注解的元注解。
//注解的属性。
//注解主要给编译器及工具类型的软件用的。
//注解的提取需要借助于 Java 的反射技术，反射比较慢，所以注解使用时也需要谨慎计较时间成本。